import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: YKH
 * Date: 2022-08-06
 * Time: 9:52
 */
public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    //创建端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    //启动服务器的方法
    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService service = Executors.newCachedThreadPool();
        while( true) {
            //accept()方法 , 如果当前没有客户端来建立连接, 就会阻塞等待!
            //与客户端建立连接
            Socket clientScoket = serverSocket.accept();  //此处的clientSocket用完之后要进行关闭, 防止内存泄漏

            // [版本一]  直接调用processConnect方法, 无法处理多个客户端的情况
            // processConnect(clientScoket);

            // [版本二]  不在直接调用 , 而是直接创建一个新的线程去调用processConnect方法,主线程执行accept()方法等待客户端创建连接
//            Thread t = new Thread(() -> {
//                try {
//                    processConnect(clientScoket);
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//            });
//            t.start();

            // [版本3] 使用线程池, 来解决频繁创建销毁线程的问题.
            // 此处不太适合使用 "固定个数的"
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnect(clientScoket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });

        }
    }

    //通过这个方法给当前连上的客户端提供服务
    //一个连接过来了 , 服务方式有两种
    //1. 短连接 : 一个连接进行一次交互( 一个请求 + 一个相应)
    //2. 长连接 : 一个连接进行多次交互( N个请求  +  N个响应)
    //此处为长连接版本
    private void processConnect(Socket clientScoket) throws IOException {
        System.out.printf("[%s:%d] 建立连接\n", clientScoket.getInetAddress().toString(),clientScoket.getPort());
        //处理连接 , 先打通输入输出的通道
        // 接收,发送数据 , 返回的是字节流 ,使用起来不太方便 , 将其封装为Scanner 和 PrintWriter
        try (InputStream inputStream = clientScoket.getInputStream();
             OutputStream outputStream = clientScoket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);

            //长连接的写法 , 需要通过循环获取多次交互的情况
            while(true) {
                //如果客户端断开连接( 挂电话) 就断开连接,跳出循环
                if( !scanner.hasNext()) {
                    //连接断开 , 当客户端断开连接时 ,此时hasNext 就会返回false
                    System.out.printf("[%s:%d] 断开连接\n",clientScoket.getInetAddress().toString(),clientScoket.getPort());
                    break;
                }

                //开始处理请求

                //1. 读取请求并解析
                String requst = scanner.next();
                //2. 根据请求计算响应
                String response = process(requst);
                //3. 把响应写回给客户端
                printWriter.println(response);
                //  刷新缓冲区, 避免数据没有真的发出去
                printWriter.flush();
                System.out.printf("[%s:%d] req : %s; resp: %s\n",
                        clientScoket.getInetAddress().toString(),
                        clientScoket.getPort(),
                        requst, response);
            }
        } finally {
            clientScoket.close(); //在此处关闭 clientSocket
        }
    }
    //服务器的业务逻辑,此处为回显服务器, 直接返回请求
    public String process( String req) {
        return req;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(8000);
        server.start();
    }

}